using System;
using System.Collections.Generic;
using System.Linq;
using Magiq.Support;

namespace Magiq.Update {
    public class EnumerableUpdate<T> : BaseUpdate<T> where T : class {
        private Dictionary<IPropertySet, Delegate> compiledChains;
        private Dictionary<IPropertySet, Delegate> compiledSettersValues;

        public EnumerableUpdate(IEnumerable<T> enumerable) {
            Where = enumerable;
        }

        public EnumerableUpdate(IEnumerable<T> enumerable, IEnumerable<IPropertySet> sets) : this(enumerable) {
            Where = enumerable;
            AddPorpertySets(sets);
        }

        public IEnumerable<T> Where { get; private set; }

        public override int Update() {
            if (PropertySets.Count == 0)
                return Where.Count();

            if (compiledSettersValues == null) {
                compiledSettersValues = new Dictionary<IPropertySet, Delegate>();
                compiledChains = new Dictionary<IPropertySet, Delegate>();

                foreach (var set in PropertySets) {
                    compiledSettersValues.Add(set, set.ValueExpression.Compile());

                    var expression = set.PropertyExpression.PreviousExpressionChain();

                    if (expression != null)
                        compiledChains.Add(set, expression.Compile());
                }
            }

            var updates = new List<object>();

            foreach (var item in Where) {
                foreach (var set in PropertySets) {
                    var returnType = set.ValueExpression.Body.Type;

                    object value = null;
                    var valueGetted = false;

                    if (set.PropertyType == returnType) {
                        try {
                            value = compiledSettersValues[set].DynamicInvoke(item);
                            valueGetted = true;
                        }
                        catch (NullReferenceException) {}
                    }
                    else {
                        returnType = set.PropertyType.IsGenericType &&
                                     set.PropertyType.GetGenericTypeDefinition() == typeof (Nullable<>)
                                         ? set.PropertyType.GetGenericArguments().First()
                                         : returnType;
                        try {
                            value = set.PropertyType == returnType
                                        ? compiledSettersValues[set].DynamicInvoke(item)
                                        :
                                            Convert.ChangeType(compiledSettersValues[set].DynamicInvoke(item),
                                                               set.PropertyType);
                            valueGetted = true;
                        }
                        catch (NullReferenceException) {}
                    }
                    if (!valueGetted) continue;

                    Delegate chain;
                    var changed = false;

                    var itemToUpdate = compiledChains.TryGetValue(set, out chain)
                                           ? chain.DynamicInvoke(item)
                                           : item;

                    try {
                        itemToUpdate.SetProperty(set.PropertyName, value);
                        changed = true;
                    }
                    catch (NullReferenceException) {}

                    if (changed && !updates.Contains(itemToUpdate))
                        updates.Add(itemToUpdate);
                }
            }
            return updates.Count;
        }
    }
}